/*****************************************************************************
 * Copyright (c) 2007, Ping Identity
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ping Identity, nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY PING IDENTITY ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL PING IDENTITY BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

#include "config.h"
#include "context.h"
#include "context_internal.h"
#include "saml_11.h"

#include <string.h>

static void do_nothing_logger(ic_ctx ctx, log_level level, 
							  const char* reason, void *log_ctx)
{
	/* do nothing implementation */
}

static void do_nothing_reporter(ic_ctx ctx, const xmlChar *name,
								const xmlChar *format, const xmlChar *value,
								void *attr_ctx)
{
	/* do nothing implementation */
}

static bool failing_detector(ic_ctx ctx, const xmlChar *replay_str,
							time_t not_on_or_after, void *detector_context)
{
	log_error(ctx, "token processing failing - no replay detection mechanism specified");
	return false; /* by default, all replay detection fails */
}


void ic_ctx_set_replay_detection(ic_ctx ctx,
							 ic_replay_detection_func detector,
							 void * detector_ctx)
{
	if (detector == NULL)
	{
		ctx->detector = failing_detector;
		ctx->detector_context = NULL;
	}
	else
	{
		ctx->detector = detector;
		ctx->detector_context = detector_ctx;
	}
}

bool ic_disable_replay_detection(ic_ctx ctx,
									const xmlChar *replay_string,
									time_t not_on_or_after,
									void *detector_context)
{
	log_reason(ctx, INFO_LVL, 
			   "replay detection disabled. Please provide some manner of secure replay detection.");
	return true;
}

void log_reasonf(struct ic_context_t *ctx, log_level level, const char *reasonstr, ...)
{
	va_list va;
	va_start (va, reasonstr);
	log_vreasonf(ctx, level, reasonstr, va);
}

void ic_logger_stdout_stderr( struct ic_context_t *ctx, log_level level, const char *reason, void *logger_ctx)
{
	switch(level) {
	case DEBUG_LVL:
		break;
	case INFO_LVL:
		fprintf(stdout, "%s\n", reason);
		break;
	case ERROR_LVL:
		fprintf(stderr, "%s\n", reason);
		break;
	}
}
void log_reason(struct ic_context_t *ctx, log_level level, const char *reason)
{
	ctx->logger(ctx, level, reason, ctx->logger_context);
}

void log_error(struct ic_context_t *ctx, const char *reason)
{
	ctx->logger(ctx, ERROR_LVL, reason, ctx->logger_context);
}

void log_vreasonf(struct ic_context_t *ctx, log_level level, const char *reasonstr, va_list args)
{
	char buffer[1024];
	if (vsnprintf(buffer, sizeof(buffer), reasonstr, args) >= (int) sizeof(buffer))
	{
		log_reason(ctx, DEBUG_LVL, "log_reasonf message exceeded buffer length");
	}
	log_reason(ctx, level, buffer);
}

struct ic_context_t *ic_ctx_create()
{
	struct ic_context_t *ctx = malloc(sizeof(struct ic_context_t));
	ctx->key_manager	= NULL;
	ctx->time			= -1;
	ctx->logger			= do_nothing_logger;
	ctx->logger_context	= NULL;
	ctx->attr_reporter	= do_nothing_reporter;
	ctx->attr_context	= NULL;
	ctx->audience		= NULL;
	ctx->detector		= failing_detector;
	ctx->detector_context = NULL;
	ctx->processor		= saml11_process_token;
	ctx->input_max_size	= 8192;
	ctx->key_manager = xmlSecKeysMngrCreate();
	if (ctx->key_manager == NULL)
	{
		log_error(ctx, "unable to get xmlsec keys manager");
		free(ctx);
		return NULL;
	}
	
	if(xmlSecCryptoAppDefaultKeysMngrInit(ctx->key_manager) < 0) {
		log_error(ctx, "failed to initialize keys manager");
		xmlSecKeysMngrDestroy(ctx->key_manager);
		free(ctx);
		return(NULL);
    } 
	
	return ctx;
}
void ic_ctx_destroy(struct ic_context_t *ctx)
{
	if (ctx->key_manager != NULL)
	{
		xmlSecKeysMngrDestroy(ctx->key_manager);
	}
	if (ctx->audience != NULL)
	{
		xmlFree(ctx->audience);
	}
	free(ctx);
}

void ic_ctx_set_logger(ic_ctx ctx, ic_logger_func logger, void *logger_ctx)
{
	if (logger != NULL)
	{
		ctx->logger = logger;
		ctx->logger_context = logger_ctx;
	}
	else
	{
		ctx->logger = do_nothing_logger;
		ctx->logger_context = NULL;
	}
}

void ic_ctx_set_attribute_reporting(ic_ctx ctx,
									ic_attribute_reporting_func reporter,
									void *reporter_ctx)
{
	if (reporter != NULL)
	{
		ctx->attr_reporter = reporter;
		ctx->attr_context = reporter_ctx;
	}
	else
	{
		ctx->attr_reporter = do_nothing_reporter;
		ctx->attr_context = NULL;
	}
}


/* decrypt the encrypted transient key;
 return success/failure; length of decrypted key in len;
 log reason if failure */
bool ic_ctx_load_certificate(struct ic_context_t *ctx, const char *cert_file_name)
{
	if (!cert_file_name || !strlen(cert_file_name)) {
		log_error(ctx, "certificate file name null or blank");
		return false;
	}
	/* get the server's certificate */
	if (xmlSecCryptoAppKeysMngrCertLoad(ctx->key_manager, cert_file_name,
									xmlSecKeyDataFormatPem,
									xmlSecKeyDataTypePublic) != 0)
	{
		log_error(ctx, "certificate could not be loaded into keys manager");
		return false;
	}
	
	return true;
}

bool ic_ctx_load_key(struct ic_context_t *ctx, const char *key_file_name, const char *password)
{
	if (!key_file_name || !strlen(key_file_name)) {
		log_error(ctx, "certificate file name null or blank");
		return false;
	}
	xmlSecKeyPtr key = xmlSecCryptoAppKeyLoad(key_file_name, xmlSecKeyDataFormatPem, password, NULL, NULL);
	if (key == NULL) {
		log_error(ctx, "unable to find or use certificate file");
		return false;
	}
	
	/* set key name to the file name, this is just an example! */
	if(xmlSecKeySetName(key, BAD_CAST key_file_name) < 0) {
		log_error(ctx, "failed to set key name");
	    xmlSecKeyDestroy(key);
	    return false;
	}
	
	/* add key to keys manager, from now on keys manager is responsible 
	 * for destroying key 
	 */
	if(xmlSecCryptoAppDefaultKeysMngrAdoptKey(ctx->key_manager, key) < 0) {
		log_error(ctx, "failed to add key to keys manager");
	    xmlSecKeyDestroy(key);
	    return false;
	}
	return true;
}

bool ic_ctx_load_pkcs12(struct ic_context_t *ctx, const char *pkcs12_file_name, const char *password)
{
	if (!pkcs12_file_name || !strlen(pkcs12_file_name)) {
		log_error(ctx, "pkcs12 file name null or blank");
		return false;
	}
	xmlSecKeyPtr key = xmlSecCryptoAppPkcs12Load(pkcs12_file_name, password, NULL, NULL);
	if (key == NULL) {
		log_error(ctx, "unable to find or use PKCS12 file");
		return false;
	}
	
	/* set key name to the file name, this is just an example! */
	if(xmlSecKeySetName(key, BAD_CAST pkcs12_file_name) < 0) {
		log_error(ctx, "failed to set PKCS12 name");
	    xmlSecKeyDestroy(key);
	    return false;
	}
	
	/* add key to keys manager, from now on keys manager is responsible 
	 * for destroying key 
	 */
	if(xmlSecCryptoAppDefaultKeysMngrAdoptKey(ctx->key_manager, key) < 0) {
		log_error(ctx, "failed to add key to keys manager");
	    xmlSecKeyDestroy(key);
	    return false;
	}
	return true;
}

time_t ic_ctx_get_current_time(struct ic_context_t *ctx)
{
	return (ctx->time == -1) ? time(NULL) : ctx->time;
}

void ic_ctx_set_current_time(ic_ctx ctx, time_t current)
{
	ctx->time = current;
}

void ic_ctx_set_audience(ic_ctx ctx, const xmlChar * audience)
{
	if (ctx->audience != NULL)
		xmlFree(ctx->audience);
	
	if (audience != NULL)
		ctx->audience = xmlStrdup(audience);
	else
		ctx->audience = NULL;
}

bool ic_ctx_is_in_audience(ic_ctx ctx, const xmlChar * audience)
{
	if (ctx->audience == NULL)
		return false;
	return xmlStrcmp(audience, ctx->audience) == 0;
}

bool ic_ctx_process_token(ic_ctx ctx, const unsigned char *input, size_t input_len)
{
	return ctx->processor(ctx, input, input_len);
}

void ic_ctx_set_max_input_size(ic_ctx ctx, unsigned int input_max_size)
{
	ctx->input_max_size = input_max_size;
}
void ic_ctx_set_token_processor(ic_ctx ctx, ic_token_processing_func processor)
{
	if (processor == NULL)
	{
		ctx->processor = saml11_process_token;
	}
	else
	{
		ctx->processor = processor;
	}
}

